﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using Asol.Reporting.Support;

namespace Asol.Reporting.Support.Components
{
    #region class DForm : základní třída všech formulářů systému
    /// <summary>
    /// DForm : základní třída všech formulářů systému.
    /// Deklaruje svůj ToolTip a objekt ToolTipSupport pro podporu řízení pozice tooltipu.
    /// </summary>
    public class DForm : Form
    {
        #region Konstrukce
        /// <summary>
        /// DForm : základní třída všech formulářů systému. Konstruktor.
        /// </summary>
        public DForm()
        {
            this.UserConfigPersistent = ConfigPersistentMode.Layout;
            this.FormConfigPersistent = ConfigPersistentMode.NotDefined;
            InitializeComponent();
            this._ToolTipSupport = new ToolTipSupport();
            this.ToolTip.Popup += new PopupEventHandler(ToolTip_Popup);
            this._ToolTipSupport.InitToolTip(this.ToolTip);
            this.Load += new EventHandler(DForm_Load);
            this.Shown += new EventHandler(DForm_Shown);
            this.FormClosed += new FormClosedEventHandler(DForm_FormClosed);
        }

        void DForm_Shown(object sender, EventArgs e)
        {
            if (!this._ConfigLoaded)
                this.ConfigLoad();
        }
        void DForm_Load(object sender, EventArgs e)
        {
            if (!this._ConfigLoaded)
                this.ConfigLoad();
        }
        private bool _ConfigLoaded;
        void DForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            this.ConfigSave();
        }
        void ToolTip_Popup(object sender, PopupEventArgs e)
        {
            this._ToolTipSupport.ToolTip_Popup(sender, e);
        }
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
        /// <summary>
        /// Zpracuje systémovou zprávu
        /// </summary>
        /// <param name="m"></param>
        protected override void WndProc(ref Message m)
        {
            bool callBase = true;
            switch (m.Msg)
            {
                case WinConst.WM_CONTEXTMENU:
                    Point clientPoint = this.PointToClient(Control.MousePosition);
                    if (clientPoint.Y < 0 && clientPoint.X > 20)
                    {
                        this._ShowTitleMenu(clientPoint);
                        callBase = false;
                    }
                    break;
                //case WinConst.WM_RBUTTONDOWN:
                //    // callBase = false;
                //    break;
                //case WinConst.WM_NCPAINT:
                //    // callBase = false;
                //    break;
            }
            if (callBase)
                base.WndProc(ref m);
        }
        #endregion
        #region Title context menu, Config mode, Load & Save (Registry)
        /// <summary>
        /// Zobrazení kontextového menu v titulkovém řádku okna
        /// </summary>
        /// <param name="clientPoint"></param>
        private void _ShowTitleMenu(Point clientPoint)
        {
            ConfigPersistentMode mode = this.UserConfigPersistent;
            this._TitleContextMenu = new ContextMenuStrip();
            this._TitleContextMenu.Items.Add(new ToolStripMenuItem("Neukládat nic") { Checked = (mode == ConfigPersistentMode.None), Tag = ConfigPersistentMode.None });
            this._TitleContextMenu.Items.Add(new ToolStripSeparator());
            this._TitleContextMenu.Items.Add(new ToolStripMenuItem("Ukládat pouze velikost okna") { Checked = (mode == ConfigPersistentMode.Size), Tag = ConfigPersistentMode.Size });
            this._TitleContextMenu.Items.Add(new ToolStripMenuItem("Ukládat i pozici okna") { Checked = (mode == ConfigPersistentMode.Bounds), Tag = ConfigPersistentMode.Bounds });
            this._TitleContextMenu.Items.Add(new ToolStripMenuItem("Ukládat i rozvržení okna") { Checked = (mode == ConfigPersistentMode.Layout), Tag = ConfigPersistentMode.Layout });
            this._TitleContextMenu.Items.Add(new ToolStripSeparator());
            this._TitleContextMenu.Items.Add(new ToolStripMenuItem("Storno"));
            this._TitleContextMenu.ItemClicked += new ToolStripItemClickedEventHandler(_TitleContextMenu_ItemClicked);
            this._TitleContextMenu.Show(Control.MousePosition);
        }
        /// <summary>
        /// Po kliknutí na položku kontextového menu v titulkovém řádku okna
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _TitleContextMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            object tag = e.ClickedItem.Tag;
            if (tag is ConfigPersistentMode)
                this.UserConfigPersistent = (ConfigPersistentMode)tag;
        }
        /// <summary>
        /// Kontextové menu v titulkovém řádku okna
        /// </summary>
        private ContextMenuStrip _TitleContextMenu;
        /// <summary>
        /// Sestaví data konfigurace tohoto okna z jeho aktuálního stavu a uloží jej do registru.
        /// </summary>
        protected void ConfigSave()
        {
            List<int> data = new List<int>();
            data.Add((int)this.UserConfigPersistent);
            data.Add((int)this.WindowState);
            Rectangle bounds = (this.WindowState == FormWindowState.Normal ? this.Bounds : this.RestoreBounds);
            data.Add(bounds.Left);
            data.Add(bounds.Top);
            data.Add(bounds.Width);
            data.Add(bounds.Height);
            int[] layout = this.ConfigUserData;
            if (layout != null)
                data.AddRange(layout);
            string config = Convertor.IEnumerableInt32ToString(data);

            Register.Set("Forms", this.CurrentConfigName, config);
        }
        /// <summary>
        /// Načte konfiguraci tohoto okna z registru, a vloží ji do this okna.
        /// </summary>
        protected void ConfigLoad()
        {
            string config;
            if (!Register.TryGetValue<string>("Forms", this.CurrentConfigName, out config)) return;

            List<int> data = Convertor.StringToListInt32(config);
            if (data == null || data.Count < 6) return;

            if (data[0] >= 0 && data[0] <= 3)
                this.UserConfigPersistent = (ConfigPersistentMode)data[0];

            ConfigPersistentMode currentMode = (this.FormConfigPersistent != ConfigPersistentMode.NotDefined ?
                this.FormConfigPersistent : this.UserConfigPersistent);

            switch (currentMode)
            {
                case ConfigPersistentMode.Size:
                    this.Size = new Size(data[4], data[5]);
                    if (data[1] >= 0 && data[1] <= 2)
                        this.WindowState = (FormWindowState)data[1];
                    break;
                case ConfigPersistentMode.Bounds:
                case ConfigPersistentMode.Layout:
                    this.Bounds = new Rectangle(data[2], data[3], data[4], data[5]);
                    if (data[1] >= 0 && data[1] <= 2)
                        this.WindowState = (FormWindowState)data[1];
                    break;
            }
            if (data.Count > 6)
                this.ConfigUserData = data.GetRange(6, data.Count - 6).ToArray();

            this._ConfigLoaded = true;
        }
        /// <summary>
        /// Aktuální celé jméno konfigurační hodnoty = jméno typu okna + ":" + specifické jméno.
        /// </summary>
        protected string CurrentConfigName
        {
            get
            {
                string name = this.GetType().NsName();
                string add = this.ConfigAddName;
                if (!String.IsNullOrEmpty(add))
                    name += ":" + add.Trim();
                return name;
            }
        }
        /// <summary>
        /// <para>
        /// Specifické jméno konfigurace (suffix) pro tuto konkrétní instanci okna.
        /// Okno ukládá svoje parametry (pozice, velikost, rozvržení) do konfigurace podle typu okna (GetType()).
        /// Konkrétní instance okna může ke jménu přidat i konkrétní dodatek jména, například podle čísla třídy, čísla záznamu atd.
        /// </para>
        /// <para>
        /// Pozor, čtení hodnoty probíhá poměrně brzy po vytvoření okna: proběhne konstruktor, 
        /// a po jeho dokončení při aktivaci okna probíhá event Load, a v jeho rámci se čte hodnota ConfigAddName a načítá se konfigurace.
        /// V případě potřeby má aplikační kód možnost po změně hodnoty ConfigAddName znovu vyvolat načtení a aplikaci konfigurace,
        /// stačí zavolat protected metodu this.ConfigLoad().
        /// Nicméně bázová třída DForm to sama od sebe (po změně hodnoty ConfigAddName) nedělá.
        /// Ona bázová třída u property ConfigAddName ani neimplementuje {set}.
        /// </para>
        /// </summary>
        protected virtual string ConfigAddName { get { return ""; } }
        /// <summary>
        /// Aktuální režim persistence údajů o okně (Config). Volí jej uživatel v kontextovém menu v titulku okna.
        /// Persistuje se vždy.
        /// </summary>
        protected virtual ConfigPersistentMode UserConfigPersistent { get; set; }
        /// <summary>
        /// Režim persistence údajů o okně (Config), řídí načítání dat z konfigurace.
        /// Explicitně daný režim zvenku: nutno zadat po konstruktoru, před Show() - má přednost před tím, co zvolil uživatel.
        /// Persistuje se vždy.
        /// </summary>
        public virtual ConfigPersistentMode FormConfigPersistent { get; set; }
        /// <summary>
        /// <para>
        /// Uživatelská konfigurace okna.
        /// </para>
        /// <para>
        /// Může obsahovat libovolné množství čísel typu Int32.
        /// Při otevírání formuláře jsou z registru načtena konfigurační data okna, a jejich uživatelská část je vložena sem.
        /// Potomek (konkrétní okno) tedy overriduje tuto property, a v metodě { set } převezme vkládané hodnoty a podle svých potřeb je zpracuje.
        /// Při zavírání okna se přečtena hodnota z této property, potomek si tedy sestaví svoje potřebná data a vrátí je.
        /// Formulář (třída DForm) je uloží do registru.
        /// </para>
        /// <para>
        /// Poznámka 1: implicitně se ukládají data všech instancí jedné třídy do jednoho registru.
        /// Přepsáním property this.ConfigAddName {get;} na potomkovi lze ukládat data jedné třídy okna do různých registrů.
        /// </para>
        /// <para>
        /// Poznámka 2: Při načítání dat {set;} je vhodné reagovat na konfiguraci this.ConfigPersistent, 
        /// a Layout restorovat jen pokus si to uživatel přeje.
        /// Při ukládání dat je vhodné ukládat všechna data bez ohledu na this.ConfigPersistent.
        /// </para>
        /// </summary>
        protected virtual int[] ConfigUserData { get; set; }
        #endregion
        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DForm));
            this.ToolTip = new System.Windows.Forms.ToolTip(this.components);
            this.SuspendLayout();
            // 
            // ToolTip
            // 
            this.ToolTip.Active = false;
            // 
            // DForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 262);
            this.Name = "DForm";
            this.ShowInTaskbar = false;
            this.Text = "Formulář";
            this.ResumeLayout(false);
        }
        /// <summary>
        /// Tooltip na formuláři
        /// </summary>
        public System.Windows.Forms.ToolTip ToolTip;
        #endregion
        #region Property
        #endregion
        #region Tooltip
        /// <summary>
        /// Podpora pro tooltip
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ToolTipSupport ToolTipSupport { get { return this._ToolTipSupport; } }
        private ToolTipSupport _ToolTipSupport;
        #endregion
        #region Umístění formuláře na souřadnici blízko daných Screen souřadnic
        /// <summary>
        /// Nastaví souřadnice this formuláře tak, aby byl co nejblíž daným souřadnicím (v koordinátech Screen).
        /// </summary>
        /// <param name="bounds"></param>
        public void SetLocationNear(Rectangle? bounds)
        {
            if (bounds == null) return;
            Rectangle? target = this._GetBoundsNear(bounds.Value);
            if (!target.HasValue) return;

            this.StartPosition = FormStartPosition.Manual;
            this.FormConfigPersistent = ConfigPersistentMode.None;
            this.Bounds = target.Value;
        }
        /// <summary>
        /// Vrátí souřadnice v koordinátech Screen, na nichž se má zobrazit this form tak, aby byl blízko daným souřadnicím (volající control), 
        /// aby je form nepřekrýval, a aby form byl celý viditelný.
        /// Pokud takové souřadnice nelze určit, vrací null. Rovněž pokud na vstupu je null, tak vrací null.
        /// </summary>
        /// <param name="bounds"></param>
        /// <returns></returns>
        private Rectangle? _GetBoundsNear(Rectangle bounds)
        {
            // Získám souřadnice monitoru, na kterém se zobrazuje daný control:
            Screen screen = System.Windows.Forms.Screen.FromRectangle(bounds);
            if (screen == null) return null;
            Rectangle area = screen.WorkingArea;

            // Určím souřadnice this formu tak, aby byl ve vhodné pozici těsně u controlu, a přitom se vešel na aktivní monitor:
            Size size = this.Size;
            Point center = new Point(bounds.Left + (bounds.Width / 2), bounds.Top + (bounds.Height / 2));
            Size half = new System.Drawing.Size((size.Width / 2), (size.Height / 2));

            int begin = 2;
            int end = 6;
            Rectangle target;
            // 1) Pozice dolů pod controlem: nejprve vodorovně vystředit, poté případně posunout na ose X doprava, doleva:
            target = new Rectangle(center.X - half.Width, bounds.Bottom + begin, size.Width, size.Height);
            if (area.Contains(target)) return target;
            if (_AreBoundsInY(area, target, begin, end))
                return _AlignBoundsInX(area, target, begin, end);

            // 2) Pozice nahoru nad controlem: nejprve vodorovně vystředit, poté případně posunout na ose X doprava, doleva:
            target = new Rectangle(center.X - half.Width, bounds.Top - end - size.Height, size.Width, size.Height);
            if (area.Contains(target)) return target;
            if (_AreBoundsInY(area, target, begin, end))
                return _AlignBoundsInX(area, target, begin, end);

            // 3) Pozice doprava od controlu: nejprve svisle vystředit, poté případně posunout na ose Y dolů, nahoru:
            target = new Rectangle(bounds.Right + begin, center.Y - half.Height, size.Width, size.Height);
            if (area.Contains(target)) return target;
            if (_AreBoundsInX(area, target, begin, end))
                return _AlignBoundsInY(area, target, begin, end);

            // 4) Pozice doleva od controlu: nejprve svisle vystředit, poté případně posunout na ose Y dolů, nahoru:
            target = new Rectangle(bounds.Left - end - size.Width, center.Y - half.Height, size.Width, size.Height);
            if (area.Contains(target)) return target;
            if (_AreBoundsInX(area, target, begin, end))
                return _AlignBoundsInY(area, target, begin, end);

            return null;
        }
        private static bool _AreBoundsInX(Rectangle area, Rectangle bounds, int begin, int end) { return (bounds.X >= (area.X + begin) && bounds.Right <= (area.Right - end)); }
        private static bool _AreBoundsInY(Rectangle area, Rectangle bounds, int begin, int end) { return (bounds.Y >= (area.Y + begin) && bounds.Bottom <= (area.Bottom - end)); }
        private Rectangle _AlignBoundsInX(Rectangle area, Rectangle bounds, int begin, int end)
        {
            int x = bounds.X;
            if ((x + bounds.Width) > (area.X + area.Width - end))
                x = (area.X + area.Width - end - bounds.Width);
            if ((x) < (area.X + begin))
                x = (area.X + begin);
            return new Rectangle(x, bounds.Y, bounds.Width, bounds.Height);
        }
        private Rectangle _AlignBoundsInY(Rectangle area, Rectangle bounds, int begin, int end)
        {
            int y = bounds.Y;
            if ((y + bounds.Height) > (area.Y + area.Height - end))
                y = (area.Y + area.Height - end - bounds.Height);
            if ((y) < (area.Y + begin))
                y = (area.Y + begin);
            return new Rectangle(bounds.X, y, bounds.Width, bounds.Height);
        }
        #endregion
    }
    /// <summary>
    /// Režimy ukládání konfigurace okna
    /// </summary>
    public enum ConfigPersistentMode
    {
        /// <summary>Neurčeno, má význam u konfigurace uložené v property FormConfigPersistent</summary>
        NotDefined = 0,
        /// <summary>Neukládá se nic</summary>
        None = 1,
        /// <summary>Ukládá se jen velikost</summary>
        Size = 2,
        /// <summary>Ukládá se velikost a pozice</summary>
        Bounds = 3,
        /// <summary>Ukládá se velikost a pozice i vnitřní rozložení</summary>
        Layout = 4
    }
    #endregion
    #region class ToolTipSupport : podpůrná třída pro zobrazování systémového tooltipu.
    /// <summary>
    /// ToolTipSupport : podpůrná třída pro zobrazování systémového tooltipu.
    /// Instancuje se automaticky na Form (jedna instance) a řeší kompletní podporu pro živý tooltip 
    /// (detekci změny pozice myši, pozicování tooltipu na pozici myši, zhasínání při opouštění controlu).
    /// </summary>
    public class ToolTipSupport
    {
        #region Konstrukce
        /// <summary>
        /// ToolTipSupport : podpůrná třída pro zobrazování systémového tooltipu. Konstruktor.
        /// </summary>
        public ToolTipSupport()
        {
            this._DataDict = new Dictionary<Control, ToolTipSupportData>();
            this.SetDefaults();
        }
        /// <summary>
        /// Nastaví default hodnoty
        /// </summary>
        protected void SetDefaults()
        {
            this.DefaultIcon = ToolTipIcon.Info;
            this.DefaultTitle = null;
            this.DefaultIsBaloon = false;
            this.IgnoreMoveBelow = 6;
            this.ToolTipOffset = new Point(8, 12);
            this._ImplicitTitle = null;
        }
        private Dictionary<Control, ToolTipSupportData> _DataDict;
        /// <summary>
        /// Příznak, zda máme zachycenu referenci na tooltip (v _ToolTip, v _ToolTipRef)
        /// </summary>
        private bool _HasTooltip
        {
            get
            {
                return (_ToolTipRef != null && _ToolTipRef.IsAlive && _ToolTipRef.Target is ToolTip);
            }
        }
        private WeakReference _ToolTipRef;
        #endregion
        #region Public property
        /// <summary>
        /// Implicitní ikona
        /// </summary>
        public ToolTipIcon DefaultIcon { get; set; }
        /// <summary>
        /// Implicitní titulek
        /// </summary>
        public string DefaultTitle { get; set; }
        /// <summary>
        /// Implicitní stav IsBaloon (vřele doporučuji false, přinejmenším pro pohyblivé tooltipy)
        /// </summary>
        public bool DefaultIsBaloon { get; set; }
        /// <summary>
        /// Weak referencovaný ToolTip.
        /// Pouze, pokud prošel naší inicializací, viz this._HasTooltip
        /// </summary>
        public ToolTip ToolTip
        {
            get
            {
                if (_HasTooltip)
                    return _ToolTipRef.Target as ToolTip;
                return null;
            }
            set
            {
                if (value == null)
                    _ToolTipRef = null;
                else
                    _ToolTipRef = new WeakReference(value);
            }
        }
        /// <summary>
        /// Ignorovat pohyb myši menší než tato vzdálenost.
        /// Implicitně 6 pixelů.
        /// </summary>
        public int IgnoreMoveBelow { get; set; }
        /// <summary>
        /// Posun bodu Tooltipu od souřadnice myši. Měl by být kladný, default = {8; 12}
        /// </summary>
        public Point ToolTipOffset { get; set; }
        #endregion
        #region Public instanční metody pro podporu definice vlastností tooltipu na instanci třídy Control
        /// <summary>
        /// Zapamatuje si, že pro daný control má mít tooltip danou ikonu a titulek.
        /// </summary>
        /// <param name="control"></param>
        /// <param name="toolTipIcon"></param>
        /// <param name="toolTipTitle"></param>
        public void SetToolTip(Control control, ToolTipIcon? toolTipIcon, string toolTipTitle)
        {
            this.SetToolTip(control, toolTipIcon, toolTipTitle, (bool?)null);
        }
        /// <summary>
        /// Zapamatuje si, že pro daný control má mít tooltip danou ikonu a titulek.
        /// </summary>
        /// <param name="control"></param>
        /// <param name="toolTipIcon"></param>
        /// <param name="toolTipTitle"></param>
        /// <param name="toolTipIsBaloon">Chcete balonový tooltip? 
        /// Očekávejte potíže, pokud budete kombinovat balonový statický tooltip s dynamickým (mouse tracking) tooltipem. 
        /// Stížnosti (ne)řeší Microsoft.</param>
        public void SetToolTip(Control control, ToolTipIcon? toolTipIcon, string toolTipTitle, bool? toolTipIsBaloon)
        {
            ToolTipSupportData data = _GetData(control);
            data.Icon = toolTipIcon;
            data.Title = toolTipTitle;
            data.IsBaloon = toolTipIsBaloon;
        }
        /// <summary>
        /// Zapamatuje si, že pro daný control má mít tooltip danou ikonu.
        /// </summary>
        /// <param name="control"></param>
        /// <param name="toolTipIcon"></param>
        public void SetToolTipIcon(Control control, ToolTipIcon toolTipIcon)
        {
            ToolTipSupportData data = _GetData(control);
            data.Icon = toolTipIcon;
        }
        /// <summary>
        /// Zapamatuje si, že pro daný control má mít tooltip daný titulek.
        /// </summary>
        /// <param name="control"></param>
        /// <param name="toolTipTitle"></param>
        public void SetToolTipTitle(Control control, string toolTipTitle)
        {
            ToolTipSupportData data = _GetData(control);
            data.Title = toolTipTitle;
        }
        /// <summary>
        /// Zapamatuje si, že pro daný control má mít tooltip daný baloon.
        /// </summary>
        /// <param name="control"></param>
        /// <param name="isBaloon"></param>
        public void SetToolTipTitleBaloon(Control control, bool isBaloon)
        {
            ToolTipSupportData data = _GetData(control);
            data.IsBaloon = isBaloon;
        }
        #endregion
        #region Public instanční metody - podpora pro zobrazení a skrývání tooltipu
        /// <summary>
        /// Zajistí rozsvícení ToolTipu pro daný control, na aktuální pozici myši, s daným textem.
        /// Tooltip není nutno předávat, pokud prošel zdejší inicializací (odchytlila se weakreference na něj).
        /// Pokud je myš v malé vzdálenosti (viz this.IgnoreMoveBelow) od bodu, kde se posledně rozsvítila, nic se dělat nebude.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="text">Text do tooltipu</param>
        public void ShowToolTipAtCurrentPosition(Control control, string text)
        {
            if (_HasTooltip)
                this._ShowToolTipAtCurrentPosition(control, this.ToolTip, text, GetDurationFromTextLength(text));
        }
        /// <summary>
        /// Zajistí rozsvícení ToolTipu pro daný control, na aktuální pozici myši, s daným textem.
        /// Tooltip není nutno předávat, pokud prošel zdejší inicializací (odchytlila se weakreference na něj).
        /// Pokud je myš v malé vzdálenosti (viz this.IgnoreMoveBelow) od bodu, kde se posledně rozsvítila, nic se dělat nebude.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="text">Text do tooltipu</param>
        /// <param name="durationByLength">Nastavit Duration podle délky textu</param>
        public void ShowToolTipAtCurrentPosition(Control control, string text, bool durationByLength)
        {
            if (_HasTooltip)
                this._ShowToolTipAtCurrentPosition(control, this.ToolTip, text, (durationByLength ? GetDurationFromTextLength(text) : this.ToolTip.AutoPopDelay));
        }
        /// <summary>
        /// Zajistí rozsvícení ToolTipu pro daný control, na aktuální pozici myši, s daným textem.
        /// Pokud je myš v malé vzdálenosti (viz this.IgnoreMoveBelow) od bodu, kde se posledně rozsvítila, nic se dělat nebude.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="toolTip">Tooltip, který se má rozsvítit</param>
        /// <param name="text">Text do tooltipu</param>
        public void ShowToolTipAtCurrentPosition(Control control, ToolTip toolTip, string text)
        {
            this._ShowToolTipAtCurrentPosition(control, toolTip, text, GetDurationFromTextLength(text));
        }
        /// <summary>
        /// Zajistí rozsvícení ToolTipu pro daný control, na aktuální pozici myši, s daným textem.
        /// Pokud je myš v malé vzdálenosti (viz this.IgnoreMoveBelow) od bodu, kde se posledně rozsvítila, nic se dělat nebude.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="toolTip">Tooltip, který se má rozsvítit</param>
        /// <param name="text">Text do tooltipu</param>
        /// <param name="durationByLength">Nastavit Duration podle délky textu</param>
        public void ShowToolTipAtCurrentPosition(Control control, ToolTip toolTip, string text, bool durationByLength)
        {
            this._ShowToolTipAtCurrentPosition(control, toolTip, text, (durationByLength ? GetDurationFromTextLength(text) : toolTip.AutoPopDelay));
        }
        /// <summary>
        /// Zajistí rozsvícení ToolTipu pro daný control, na aktuální pozici myši, s daným textem.
        /// Pokud je myš v malé vzdálenosti (viz this.IgnoreMoveBelow) od bodu, kde se posledně rozsvítila, nic se dělat nebude.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="toolTip">Tooltip, který se má rozsvítit</param>
        /// <param name="text">Text do tooltipu</param>
        /// <param name="duration">Doba, po kterou bude tooltip svítit</param>
        public void ShowToolTipAtCurrentPosition(Control control, ToolTip toolTip, string text, int duration)
        {
            this._ShowToolTipAtCurrentPosition(control, toolTip, text, duration);
        }
        /// <summary>
        /// Zajistí rozsvícení ToolTipu pro daný control, na dané pozici (pozice v rámci Controlu!), s daným textem.
        /// Tooltip není nutno předávat, pokud prošel zdejší inicializací (odchytlila se weakreference na něj).
        /// Pokud je myš v malé vzdálenosti (viz this.IgnoreMoveBelow) od bodu, kde se posledně rozsvítila, nic se dělat nebude.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="point">Bod pro rozsvícení tooltipu</param>
        /// <param name="text">Text do tooltipu</param>
        public void ShowToolTipAtPoint(Control control, Point point, string text)
        {
            if (_HasTooltip)
                this._ShowToolTipAtPoint(control, point, this.ToolTip, text, GetDurationFromTextLength(text));
        }
        /// <summary>
        /// Zajistí rozsvícení ToolTipu pro daný control, na dané pozici (pozice v rámci Controlu!), s daným textem.
        /// Tooltip není nutno předávat, pokud prošel zdejší inicializací (odchytlila se weakreference na něj).
        /// Pokud je myš v malé vzdálenosti (viz this.IgnoreMoveBelow) od bodu, kde se posledně rozsvítila, nic se dělat nebude.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="point">Bod pro rozsvícení tooltipu</param>
        /// <param name="text">Text do tooltipu</param>
        /// <param name="durationByLength">Nastavit Duration podle délky textu</param>
        public void ShowToolTipAtPoint(Control control, Point point, string text, bool durationByLength)
        {
            if (_HasTooltip)
                this._ShowToolTipAtPoint(control, point, this.ToolTip, text, (durationByLength ? GetDurationFromTextLength(text) : this.ToolTip.AutoPopDelay));
        }
        /// <summary>
        /// Zajistí rozsvícení ToolTipu pro daný control, na dané pozici (pozice v rámci Controlu!), s daným textem.
        /// Pokud je myš v malé vzdálenosti (viz this.IgnoreMoveBelow) od bodu, kde se posledně rozsvítila, nic se dělat nebude.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="point">Bod pro rozsvícení tooltipu</param>
        /// <param name="toolTip">Tooltip, který se má rozsvítit</param>
        /// <param name="text">Text do tooltipu</param>
        public void ShowToolTipAtPoint(Control control, Point point, ToolTip toolTip, string text)
        {
            this._ShowToolTipAtPoint(control, point, toolTip, text, GetDurationFromTextLength(text));
        }
        /// <summary>
        /// Zajistí rozsvícení ToolTipu pro daný control, na dané pozici (pozice v rámci Controlu!), s daným textem.
        /// Pokud je myš v malé vzdálenosti (viz this.IgnoreMoveBelow) od bodu, kde se posledně rozsvítila, nic se dělat nebude.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="point">Bod pro rozsvícení tooltipu</param>
        /// <param name="toolTip">Tooltip, který se má rozsvítit</param>
        /// <param name="text">Text do tooltipu</param>
        /// <param name="durationByLength">Nastavit Duration podle délky textu</param>
        public void ShowToolTipAtPoint(Control control, Point point, ToolTip toolTip, string text, bool durationByLength)
        {
            this._ShowToolTipAtPoint(control, point, toolTip, text, (durationByLength ? GetDurationFromTextLength(text) : toolTip.AutoPopDelay));
        }
        /// <summary>
        /// Zajistí rozsvícení ToolTipu pro daný control, na dané pozici (pozice v rámci Controlu!), s daným textem.
        /// Pokud je myš v malé vzdálenosti (viz this.IgnoreMoveBelow) od bodu, kde se posledně rozsvítila, nic se dělat nebude.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="point">Bod pro rozsvícení tooltipu</param>
        /// <param name="toolTip">Tooltip, který se má rozsvítit</param>
        /// <param name="text">Text do tooltipu</param>
        /// <param name="duration">Doba, po kterou bude tooltip svítit</param>
        public void ShowToolTipAtPoint(Control control, Point point, ToolTip toolTip, string text, int duration)
        {
            this._ShowToolTipAtPoint(control, point, toolTip, text, duration);
        }
        /// <summary>
        /// Podpora pro externí metodu, která chce rozsvítit tooltip pro daný control na dané pozici myši.
        /// Tooltip sám nebudeme rozsvěcovat, zde pouze určíme zda se rozsvítit má a kde.
        /// Pokud ano, bude vyvolána akce (parametr action), již se předá bod (Point), na kterém se má tooltip rozsvítit.
        /// Akce (typicky anonymní metoda) je tedy spuštěna jen tehdy, když se má tooltip rozsvítit, a jako parametr dostane konkrétní bod.
        /// Akce může teprve poté určit text tooltipu, nastavit titulek, zadat trvání tooltipu atd, a zobrazit si jej.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="action">
        /// Akce, která se zavolá v případě, kdy se má rozsvítit tooltip.
        /// Pro jistotu: anonymní metoda se píše: "delegate(Point point) { _ToolTip.Show("Text", control, point); }", kde "control" je to, co se předává i sem jakožto control.
        /// Kdo to splete (sem pošle jeden control, a tooltip zobrazí pro dočista jiný control), nesmí se divit, že se mu tooltip zobrazí někde kdesi mimo mísu!!!
        /// </param>
        public void PrepareToolTipForCurrentPosition(Control control, Action<Point> action)
        {
            Point point;
            if (action != null && _DetectMousePositionChanged(control, out point))
                action(point);
        }
        /// <summary>
        /// Vrátí přiměřenou dobu svícení tooltipu pro daný text.
        /// Pracuje se s tím, že uživatel dokáže přečíst cca 30 znaků za sekundu.
        /// Minimum nechť je 1,6 sekundy.
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static int GetDurationFromTextLength(string text)
        {
            float duration = 1.9f;
            if (text != null && text.Length > 48)
                duration = 0.6f + (float)text.Length / 28f;
            return (int)(duration * 1000f);
        }
        /// <summary>
        /// Zajistí zhasnutí implicitního tooltipu pro daný control.
        /// </summary>
        /// <param name="control"></param>
        public void HideToolTipForControl(Control control)
        {
            if (_HasTooltip)
                this._HideToolTipForControl(control, this.ToolTip);
        }
        /// <summary>
        /// Zajistí zhasnutí tooltipu pro daný control.
        /// </summary>
        /// <param name="control"></param>
        /// <param name="toolTip"></param>
        public void HideToolTipForControl(Control control, ToolTip toolTip)
        {
            this._HideToolTipForControl(control, toolTip);
        }
        #endregion
        #region Podpora pro inicializaci tooltipu
        /// <summary>
        /// Inicializuje tooltip.
        /// Současně si na něj uchová nehasnoucí vzpomínku (v podobě WeakReference).
        /// Pak lze využívat jiné metody bez explicitního uvedení tooltipu.
        /// </summary>
        /// <param name="toolTip"></param>
        public void InitToolTip(ToolTip toolTip)
        {
            _InitToolTip(toolTip, false, ToolTipIcon.None, "Informace");
        }
        /// <summary>
        /// Inicializuje tooltip.
        /// Současně si na něj uchová nehasnoucí vzpomínku (v podobě WeakReference).
        /// Pak lze využívat jiné metody bez explicitního uvedení tooltipu.
        /// </summary>
        /// <param name="toolTip">Objekt tooltipu</param>
        /// <param name="isBaloon">Příznak, že chceme balonový tooltip. Pro tooltip, který reaguje na pohyb myši se VŘELE NEDOPORUČUJE.</param>
        public void InitToolTip(ToolTip toolTip, bool isBaloon)
        {
            _InitToolTip(toolTip, isBaloon, ToolTipIcon.None, "Informace");
        }
        /// <summary>
        /// Inicializuje tooltip.
        /// Současně si na něj uchová nehasnoucí vzpomínku (v podobě WeakReference).
        /// Pak lze využívat jiné metody bez explicitního uvedení tooltipu.
        /// </summary>
        /// <param name="toolTip">Objekt tooltipu</param>
        /// <param name="icon">Ikona tooltipu</param>
        /// <param name="title">Titulní (tučný) řádek tooltipu</param>
        public void InitToolTip(ToolTip toolTip, ToolTipIcon icon, string title)
        {
            _InitToolTip(toolTip, false, icon, title);
        }
        /// <summary>
        /// Inicializuje tooltip.
        /// Současně si na něj uchová nehasnoucí vzpomínku (v podobě WeakReference).
        /// Pak lze využívat jiné metody bez explicitního uvedení tooltipu.
        /// </summary>
        /// <param name="toolTip">Objekt tooltipu</param>
        /// <param name="isBaloon">Příznak, že chceme balonový tooltip. Pro tooltip, který reaguje na pohyb myši se VŘELE NEDOPORUČUJE.</param>
        /// <param name="icon">Ikona tooltipu</param>
        /// <param name="title">Titulní (tučný) řádek tooltipu</param>
        public void InitToolTip(ToolTip toolTip, bool isBaloon, ToolTipIcon icon, string title)
        {
            _InitToolTip(toolTip, isBaloon, icon, title);
        }
        /// <summary>
        /// Inicializace
        /// </summary>
        /// <param name="toolTip">Objekt tooltipu</param>
        /// <param name="isBaloon">Příznak, že chceme balonový tooltip. Pro tooltip, který reaguje na pohyb myši se VŘELE NEDOPORUČUJE.</param>
        /// <param name="icon">Ikona tooltipu</param>
        /// <param name="title">Titulní (tučný) řádek tooltipu</param>
        private void _InitToolTip(ToolTip toolTip, bool isBaloon, ToolTipIcon icon, string title)
        {
            toolTip.Active = true;                     // true !   Jinak se přes Show nerozsvítí
            toolTip.AutomaticDelay = 350;
            toolTip.AutoPopDelay = (isBaloon ? 3500 : 2400);
            toolTip.BackColor = SystemColors.Info;
            toolTip.InitialDelay = (isBaloon ? 350 : 100);
            toolTip.IsBalloon = isBaloon;              // IsBalloon = true : vůbec se nehodí k použití spolu s řízením pozice (Show at Point). Bliká, a to včetně pozadí!!!
            toolTip.ReshowDelay = 650;
            toolTip.ShowAlways = isBaloon;
            toolTip.ToolTipIcon = icon;
            toolTip.ToolTipTitle = title;
            toolTip.UseAnimation = isBaloon;
            toolTip.UseFading = isBaloon;

            // Opíšeme si ikonu a titulek:
            this.DefaultIcon = icon;
            this.DefaultTitle = title;
            this.DefaultIsBaloon = isBaloon;

            // Uchováme si WeakReferenci:
            this.ToolTip = toolTip;
        }
        #endregion
        #region Eventy ToolTipu
        /// <summary>
        /// Handler pro událost, kdy se zobrazuje tooltip.
        /// Událost vyvolává ToolTip, reaguje na ni DForm, a předává ji nám (protože my jsme v DForm hostováni).
        /// Zde můžeme ještě nastavit vlastnosti tooltipu podle konkrétního objektu, pro který se tooltip rozsvěcuje.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        internal void ToolTip_Popup(object sender, PopupEventArgs e)
        {
            ToolTip toolTip = sender as ToolTip;
            ToolTipSupportData data = _GetData(e.AssociatedControl);

            // Ikona:
            if (data.Icon.HasValue)
                toolTip.ToolTipIcon = data.Icon.Value;
            else
                toolTip.ToolTipIcon = this.DefaultIcon;

            // Titulek:
            if (data.Title != null)
                toolTip.ToolTipTitle = data.Title;
            else
                toolTip.ToolTipTitle = this.DefaultTitle;

            // Baloon:
            if (data.IsBaloon.HasValue)
                toolTip.IsBalloon = data.IsBaloon.Value;
            else
                toolTip.IsBalloon = this.DefaultIsBaloon;
        }
        #endregion
        #region Static services
        /// <summary>
        /// Vrací pozici, na které v rámci controlu začíná jeho vnitřní užitná plocha.
        /// Control jako celek zabírá i border + title, ale vnitřní plocha se počítá bez těchto vnějších prvků.
        /// </summary>
        /// <param name="control"></param>
        /// <returns></returns>
        public static Point GetControlClientOffset(Control control)
        {
            Size sizeIn = control.ClientSize;
            Size sizeOut = control.Size;
            int borderL = (sizeOut.Width - sizeIn.Width) / 2;
            int borderT = sizeOut.Height - sizeIn.Height - borderL;
            return new Point(borderL, borderT);
        }
        /// <summary>
        /// Vrátí tu vyšší vzdálenost mezi dvěma body ve směru X nebo Y, vrací kladné číslo nebo (0 když jsou body identické)
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <returns></returns>
        public static int GetDistanceXY(Point p1, Point p2)
        {
            int dx = Math.Abs(p1.X - p2.X);
            int dy = Math.Abs(p1.Y - p2.Y);
            return Math.Max(dx, dy);
        }
        #endregion
        #region Private metody pro řízení tooltipu
        /// <summary>
        /// Metoda určí, zda aktuální pozice myši má způsobit rozsvícení tooltipu (řeší pohyb / nepohyb),
        /// a kde se má tooltip rozsvítit (řeší konverzi souřadnic myši na souřadnice v prostoru Controlu).
        /// Výstupem je souřadnice bodu, na kterém se má rozsvítit tooltip pro daný control.
        /// Je odvozen od souřadnice myši relativné k počátku controlu s přídavkem ToolTipOffset
        /// </summary>
        /// <param name="control"></param>
        /// <param name="point"></param>
        /// <returns></returns>
        private bool _DetectMousePositionChanged(Control control, out Point point)
        {
            Point mousePosition = Control.MousePosition;
            ToolTipSupportData data = _GetData(control);
            point = Point.Empty;

            // Pokud byl nedávno tooltip zobrazen, a nebyl zhasnut (viz metoda this._OnMouseLeave()), a pokud se myš od poslední známé pozice moc nevzdálila, pak nebudeme nic řešit:
            if (data.ToolTipIsShowed)
            {
                int distance = GetDistanceXY(mousePosition, data.LastMousePosition);
                if (distance == 0 || distance < this.IgnoreMoveBelow) return false;
            }

            data.LastMousePosition = mousePosition;

            // Určím relativní souřadnici myši v prostoru daného Controlu:
            if (control is Form)
                point = new Point(
                    mousePosition.X - control.Location.X + this.ToolTipOffset.X,
                    mousePosition.Y - control.Location.Y + this.ToolTipOffset.Y);
            else
            {
                Point clientInScreen = control.PointToScreen(new Point(0, 0));       // Absolutní souřadnice klientské části controlu (nikoli ale control jako takový)
                Point clientOffset = GetControlClientOffset(control);                // Posun klientské části controlu proti jeho vlastnímu počátku controlu
                point = new Point(
                    mousePosition.X - (clientInScreen.X - clientOffset.X) + this.ToolTipOffset.X,
                    mousePosition.Y - (clientInScreen.Y - clientOffset.Y) + this.ToolTipOffset.Y);
            }

            data.ToolTipIsShowed = true;
            return true;
        }
        /// <summary>
        /// Zajistí rozsvícení ToolTipu pro daný control, na aktuální pozici myši, s daným textem.
        /// Pokud je myš v malé vzdálenosti (viz this.IgnoreMoveBelow) od bodu, kde se posledně rozsvítila, nic se dělat nebude.
        /// </summary>
        /// <param name="control">Control, o nějž se jedná</param>
        /// <param name="toolTip">Tooltip, který se má rozsvítit</param>
        /// <param name="text">Text do tooltipu</param>
        /// <param name="duration">Doba, po kterou bude tooltip svítit</param>
        private void _ShowToolTipAtCurrentPosition(Control control, ToolTip toolTip, string text, int duration)
        {
            Point point;
            if (_DetectMousePositionChanged(control, out point))
                this._ShowToolTipAtPoint(control, point, toolTip, text, duration);
        }
        /// <summary>
        /// Rozsvítí tooltip na dané pozici.
        /// Řeší explicitní / implicitní titulek a ikonu.
        /// </summary>
        /// <param name="control"></param>
        /// <param name="toolTip"></param>
        /// <param name="point"></param>
        /// <param name="text"></param>
        /// <param name="duration"></param>
        private void _ShowToolTipAtPoint(Control control, Point point, ToolTip toolTip, string text, int duration)
        {
            this._SetIconAndTitle(control, toolTip);
            toolTip.Show(text, control, point, duration);
        }
        /// <summary>
        /// Řeší explicitní / implicitní ikonu a titulek tooltipu.
        /// </summary>
        /// <param name="control"></param>
        /// <param name="toolTip"></param>
        private void _SetIconAndTitle(Control control, ToolTip toolTip)
        {
            ToolTipIcon? exToolTipIcon = null;
            string exToolTipTitle = null;
            if (control is IToolTipExtendedData)
            {
                IToolTipExtendedData extended = control as IToolTipExtendedData;
                exToolTipIcon = extended.ToolTipIcon;
                exToolTipTitle = extended.ToolTipTitle;
            }

            // Ikona:
            if (exToolTipIcon != null)
                toolTip.ToolTipIcon = exToolTipIcon.Value;
            else
                toolTip.ToolTipIcon = this.DefaultIcon;

            // Titulek:
            if (exToolTipTitle != null)
                toolTip.ToolTipTitle = exToolTipTitle;
            else
                toolTip.ToolTipTitle = this._ImplicitTitle;
        }
        private string _ImplicitTitle;
        /// <summary>
        /// Zhasne tooltip a zapíše si to za uši (tj. do paměti)
        /// </summary>
        /// <param name="control"></param>
        /// <param name="toolTip"></param>
        private void _HideToolTipForControl(Control control, ToolTip toolTip)
        {
            if (this._DataDict.ContainsKey(control))
                this._DataDict[control].ToolTipIsShowed = false;
            toolTip.Hide(control);
        }
        /// <summary>
        /// Najde nebo založí data pro control a vrátí je.
        /// Vždy vrací data, kromě případu kdy control je null.
        /// </summary>
        /// <param name="control"></param>
        /// <returns></returns>
        private ToolTipSupportData _GetData(Control control)
        {
            ToolTipSupportData data;
            if (!this._DataDict.TryGetValue(control, out data))
            {
                data = new ToolTipSupportData(control);
                this._DataDict.Add(control, data);
            }
            return data;
        }
        #endregion
        #region subclass ToolTipSupportData
        private class ToolTipSupportData
        {
            internal ToolTipSupportData(Control control)
            {
                this.Control = control;
            }
            internal Control Control;
            internal ToolTipIcon? Icon;
            internal string Title;
            internal bool? IsBaloon;
            internal Point LastMousePosition;
            internal bool ToolTipIsShowed;
        }
        #endregion
    }
    #endregion
    #region interface IToolTipExtendedData : předepisuje vlastnosti objektu, které mohou ovlivnit vzhled tooltipu pro tento objekt
    /// <summary>
    /// IToolTipExtendedData : předepisuje vlastnosti objektu, které mohou ovlivnit vzhled tooltipu pro tento objekt
    /// </summary>
    public interface IToolTipExtendedData
    {
        /// <summary>
        /// Ikona pro ToolTip pro tento control.
        /// Pokud je null, ikona se nemění.
        /// </summary>
        ToolTipIcon? ToolTipIcon { get; }
        /// <summary>
        /// Titulek pro ToolTip pro tento control.
        /// Pokud je null, titulek se nemění.
        /// Pokud je String.Empty, je ToolTip pro tento control zobrazen bez titulku.
        /// </summary>
        string ToolTipTitle { get; }
    }
    #endregion
    #region class : FormMoveSupport
    /// <summary>
    /// Třída, která podporuje přesouvání formuláře za jeho tělo (a za jeho zvolené controly), nejen za titulek.
    /// Je tak možno přesouvat i formulář bez borderu.
    /// Použití: na formuláři se vytvoří instanční proměnná typu FormMoveSupport, do konstruktoru se předá hostitelský form.
    /// Tím se form (jeho vlastní plocha) stane přesouvatelná.
    /// Následně je možno zaregistrovat libovolný další control (metodou this.RegisterControl(control)),
    /// který se pak rovněž stane aktivním z hlediska přesouvání.
    /// </summary>
    public class FormMoveSupport
    {
        #region Public vrstva
        /// <summary>
        /// Konstruktor, předává se Form který se bude přesouvat.
        /// </summary>
        /// <param name="form"></param>
        public FormMoveSupport(Form form)
        {
            this._Form = form;
            this.RegisterControl(form);
        }
        /// <summary>
        /// Konstruktor, předává se Form který se bude přesouvat.
        /// Současně lze předat controly, které se mají zaregistrovat.
        /// </summary>
        /// <param name="form"></param>
        public FormMoveSupport(Form form, params Control[] controls)
        {
            this._Form = form;
            this.RegisterControl(form);

            foreach (Control control in controls)
                RegisterControl(control);
        }
        /// <summary>
        /// Registrace libovolného controlu.
        /// This se zaháčkuje do jeho Mouse eventů a ošetří je tak, aby bylo možno jejich přesouváním přesouvat Form.
        /// </summary>
        /// <param name="control"></param>
        public void RegisterControl(Control control)
        {
            control.MouseDown += new MouseEventHandler(_MouseDown);
            control.MouseMove += new MouseEventHandler(_MouseMove);
            control.MouseUp += new MouseEventHandler(_MouseUp);
        }
        #endregion
        #region Privátní vrstva
        void _MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Left) return;

            Point fp = this._Form.Location;
            Point mp = Control.MousePosition;
            Point df = new Point(mp.X - fp.X, mp.Y - fp.Y);
            this._FormToMouseOffset = df;

            Size ds = SystemInformation.DragSize;
            this._InactiveMouseArea = new Rectangle(mp.X - ds.Width / 2, mp.Y - ds.Height / 2, ds.Width, ds.Height);

            this._Notificator = sender as IFormMoveNotification;
            this._NotifyState(FormMoveNotificationState.MouseDown);
        }
        void _MouseMove(object sender, MouseEventArgs e)
        {
            if (!this._IsMouseDown) return;

            Point mp = Control.MousePosition;
            if (this._IsWaitToMove)
            {
                if (this._InactiveMouseArea.Value.Contains(mp)) return;
                this._InactiveMouseArea = null;
                this._NotifyState(FormMoveNotificationState.BeginMove);
            }

            Point df = this._FormToMouseOffset.Value;
            Point fp = new Point(mp.X - df.X, mp.Y - df.Y);
            this._Form.Location = fp;

            this._NotifyState(FormMoveNotificationState.Move);
        }
        void _MouseUp(object sender, MouseEventArgs e)
        {
            this._NotifyState((this._IsMoveForm ? FormMoveNotificationState.DoneMove : FormMoveNotificationState.MouseUp));
            this._FormToMouseOffset = null;
            this._Notificator = null;
        }
        private void _NotifyState(FormMoveNotificationState state)
        {
            if (this._HasNotificator)
                this._Notificator.FormMoveNotification(this, new FormMoveNotificationArgs(state));
        }
        private Form _Form;
        private Point? _FormToMouseOffset;
        private Rectangle? _InactiveMouseArea;
        /// <summary>
        /// Jsme ve stavu, kdy je myš stisknuta. Tato property neřeší, zda se čeká na pohyb, nebo se pohybuje.
        /// </summary>
        private bool _IsMouseDown { get { return (this._FormToMouseOffset.HasValue); } }
        /// <summary>
        /// Jsme ve stavu, kdy je myš stisknuta ale ještě se nepohnula natolik, aby to zahájilo pohyb (čekáme na pohyb)
        /// </summary>
        private bool _IsWaitToMove { get { return (this._IsMouseDown && this._InactiveMouseArea.HasValue); } }
        /// <summary>
        /// Jsme ve stavu, kdy je myš stisknuta a pohybuje se tak daleko, že to pohybuje formulářem.
        /// </summary>
        private bool _IsMoveForm { get { return (this._IsMouseDown && !this._InactiveMouseArea.HasValue); } }
        private IFormMoveNotification _Notificator;
        private bool _HasNotificator { get { return (this._Notificator != null); } }
        #endregion
    }
    #endregion
    #region interface IFormMoveNotification + Args + State
    /// <summary>
    /// Interface, skrz který může správce pohybu formuláře FormMoveSupport 
    /// předávat informace o pohybu do toho objektu, který je zprostředkovatelem pohybu.
    /// Jinými slovy: pokud máme na formuláři button, který se zaregistroval jako control který umožňuje přesouvat formulář,
    /// pak tímto interfacem bude dostávat zprávy o pohybu (zahájení, průběh, konec).
    /// </summary>
    public interface IFormMoveNotification
    {
        /// <summary>
        /// Objekt zde dostává informaci o změně stavu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void FormMoveNotification(object sender, FormMoveNotificationArgs args);
    }
    /// <summary>
    /// Informace o stavu pohybu formuláře skrze FormMoveSupport
    /// </summary>
    public class FormMoveNotificationArgs
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="state"></param>
        public FormMoveNotificationArgs(FormMoveNotificationState state)
        {
            this.State = state;
        }
        /// <summary>
        /// Stav procesu přesouvání (začátek, průběh, konec)
        /// </summary>
        public FormMoveNotificationState State { get; private set; }
    }
    /// <summary>
    /// Stav pohybu formuláře skrze FormMoveSupport
    /// </summary>
    public enum FormMoveNotificationState
    {
        /// <summary>
        /// Není změna
        /// </summary>
        None,
        /// <summary>
        /// Stiskla se myš, ale to ještě neznamená zahájení pohybu.
        /// </summary>
        MouseDown,
        /// <summary>
        /// Zvedla se myš, aniž by došlo k pohybu.
        /// </summary>
        MouseUp,
        /// <summary>
        /// Stisknutá myš se začala pohybovat nad rámec chvění.
        /// </summary>
        BeginMove,
        /// <summary>
        /// Probíhá přesun.
        /// </summary>
        Move,
        /// <summary>
        /// Končí přesun. Poté už nebude volána notifikace se stavem MouseUp.
        /// </summary>
        DoneMove
    }
    #endregion
    #region class DSplitContainer : SplitContainer + property
    /// <summary>
    /// DSplitContainer : splitter s doplněnými properties
    /// </summary>
    public class DSplitContainer : SplitContainer
    {
        #region Public property
        /// <summary>
        /// Obsahuje pozici splitetru měřenou z pohledu fixního panelu.
        /// Na rozdíl od hodnoty SplitterDistance, která je měřena vždy pro Panel1 (= zleva/zvrchu), tak SplitterPosition reaguje na hodnotu this.FixedPanel (určující, který panel je fixní).
        /// Pokud není fixní žádný panel, pak při Resize se Splitter pohybuje proporcionálně, a zdejší SplitterPosition vrací jeho pozici v promilích (aby bylo možno jej typovat stále jako Int32).
        /// </summary>
        [Browsable(true)]
        [Category("DSplitContainer")]
        [Description("Obsahuje pozici splitteru měřenou z pohledu fixního panelu")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int SplitterPosition
        {
            get
            {
                switch (this.FixedPanel)
                {
                    case System.Windows.Forms.FixedPanel.Panel1:
                        return this.SplitterDistance;
                    case System.Windows.Forms.FixedPanel.Panel2:
                        if (this.Orientation == System.Windows.Forms.Orientation.Horizontal)
                        {
                            return this.Height - this.SplitterDistance;
                        }
                        return this.Width - this.SplitterDistance;
                    case System.Windows.Forms.FixedPanel.None:
                        if (this.Orientation == System.Windows.Forms.Orientation.Horizontal)
                        {
                            if (this.Height == 0) return 0;
                            return 1000 * this.SplitterDistance / this.Height;
                        }
                        if (this.Width == 0) return 0;
                        return 1000 * this.SplitterDistance / this.Width;
                    default:
                        return this.SplitterDistance;
                }
            }
            set
            {
                switch (this.FixedPanel)
                {
                    case System.Windows.Forms.FixedPanel.Panel1:
                        this.SplitterDistance = value;
                        break;
                    case System.Windows.Forms.FixedPanel.Panel2:
                        if (this.Orientation == System.Windows.Forms.Orientation.Horizontal)
                            this.SplitterDistance = this.Height - value;
                        else
                            this.SplitterDistance = this.Width - value;
                        break;
                    case System.Windows.Forms.FixedPanel.None:
                        if (this.Orientation == System.Windows.Forms.Orientation.Horizontal)
                            this.SplitterDistance = value * this.Height / 1000;
                        else
                            this.SplitterDistance = value * this.Width / 1000;
                        break;
                    default:
                        this.SplitterDistance = value;
                        break;
                }
            }
        }
        #endregion
    }
    #endregion
    #region DButton
    /// <summary>
    /// Button s přednastaveným zarovnáním textu a Image
    /// </summary>
    public class DButton : Button
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        public DButton()
        {
            this.TextAlign = ContentAlignment.MiddleCenter;
            this.TextImageRelation = TextImageRelation.ImageBeforeText;
            this.ImageAlign = ContentAlignment.MiddleRight; // MiddleCenter;
        }
    }
    #endregion
    #region DComboBox
    /// <summary>
    /// Combobox s implementací správy objektů
    /// </summary>
    public class DComboBox : ComboBox
    {
        /// <summary>
        /// Položky nabídky DComboBoxu typované na DComboItem
        /// </summary>
        public IEnumerable<DComboItem> DItems
        {
            get
            {
                List<DComboItem> dItems = new List<DComboItem>();
                foreach (object item in this.Items)
                {
                    DComboItem dItem = item as DComboItem;
                    if (dItem != null)
                        dItems.Add(dItem);
                }
                return dItems;
            }
            set
            {
                this.Items.Clear();
                if (value != null)
                    this.Items.AddRange(value.ToArray());
            }
        }
        /// <summary>
        /// Aktuálně vybraná položka typovaná na DComboItem
        /// </summary>
        public DComboItem SelectedDItem
        {
            get
            {
                return this.SelectedItem as DComboItem;
            }
            set
            {
                this.SelectedItem = value;
            }
        }
        /// <summary>
        /// Metoda najde v položkách Combo boxu objekt s daným ID
        /// </summary>
        /// <param name="findID"></param>
        /// <returns></returns>
        public DComboItem ComboFindDItem(IComparable findID)
        {
            foreach (object item in this.Items)
            {
                DComboItem dItem = item as DComboItem;
                if (dItem != null && dItem.ID.CompareTo(findID) == 0)
                    return dItem;
            }
            return null;
        }
    }
    /// <summary>
    /// Objekt, který nese ID, text a libovolná data.
    /// Typicky jde o položku ListBoxu nebo ComboBoxu, kde se zobrazuje text a současně je obsažen libovolný datový objekt.
    /// </summary>
    public class DComboItem
    {
        public DComboItem(IComparable id)
        {
            this.ID = id;
        }
        public DComboItem(IComparable id, string text)
        {
            this.ID = id;
            this.Text = text;
        }
        public DComboItem(IComparable id, string text, object data)
        {
            this.ID = id;
            this.Text = text;
            this.Data = data;
        }
        public override string ToString()
        {
            return this.Text;
        }
        public IComparable ID { get; set; }
        public string Text { get; set; }
        public object Data { get; set; }
    }
    #endregion
    #region DPanel
    /// <summary>
    /// Bázová třída panelu
    /// </summary>
    public class DPanel : Panel
    { }
    #endregion
}
